//-----------------------------------------------------------------------------
// Filename: UASInviteTransaction.cs
//
// Description: SIP Transaction that implements UAS (User Agent Server) functionality for
// an INVITE transaction.
// 
// History:
// 21 Nov 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using BlueFace.VoIP.Authentication;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.Net.SIP
{
	/// <summary>
	/// The server transaction for an INVITE request. This transaction processes incoming calls RECEIVED by the application.
	/// </summary>
	public class UASInviteTransaction : SIPTransaction
    {
        private static string m_sipProxyUserAgent = SIPConstants.SIP_PROXYUSERAGENT_STRING;

        public event SIPTransactionCancelledDelegate UASInviteTransactionCancelled;
        public event SIPTransactionAuthenticationRequiredDelegate UASInviteTransactionRequestAuthenticate;
        public event SIPTransactionRequestReceivedDelegate UASInviteTransactionRequestReceived;
        public event SIPTransactionTimedOutDelegate UASInviteTransactionTimedOut;

        public SIPCDR CDR;

        private bool m_authenticationRequired = false;
        private string m_realm;

        public UASInviteTransaction(
            SIPTransport sipTransport,
            SIPRequest sipRequest,
            IPEndPoint dstEndPoint,
            IPEndPoint sendFromEndPoint,
            SIPProtocolsEnum protocol,
            bool authenticationRequired,
            string realm)
            : base(sipTransport, sipRequest, dstEndPoint, sendFromEndPoint, protocol)
		{
            m_authenticationRequired = authenticationRequired;
            m_realm = realm;
            TransactionType = SIPTransactionTypesEnum.Invite;
            m_remoteTag = sipRequest.Header.From.FromTag;

            if (sipRequest.Header.To.ToTag == null)
            {
                // This UAS needs to set the To Tag.
                m_localTag = CallProperties.CreateNewTag();
            }
            else
            {
                // This is a re-INVITE.
                m_localTag = sipRequest.Header.To.ToTag;
            }

            //logger.Debug("New UASTransaction (" + TransactionId + ") for " + TransactionRequest.URI.ToString() + " to " + RemoteEndPoint + ".");

            CDR = new SIPCDR(SIPCallDirection.In, sipRequest.URI, sipRequest.Header.From, sipRequest.Header.CallId, sendFromEndPoint, dstEndPoint);

            //UpdateTransactionState(SIPTransactionStatesEnum.Proceeding);

            TransactionRequestReceived += new SIPTransactionRequestReceivedDelegate(UASInviteTransaction_TransactionRequestReceived);
            TransactionInformationResponseReceived += new SIPTransactionResponseReceivedDelegate(UASInviteTransaction_TransactionResponseReceived);
            TransactionFinalResponseReceived += new SIPTransactionResponseReceivedDelegate(UASInviteTransaction_TransactionResponseReceived);
            TransactionTimedOut += new SIPTransactionTimedOutDelegate(UASInviteTransaction_TransactionTimedOut);
            TransactionRemoved += new SIPTransactionRemovedDelegate(UASInviteTransaction_TransactionRemoved);
        }

        private void UASInviteTransaction_TransactionRemoved(SIPTransaction transaction)
        {
            // Remove event handlers.
            UASInviteTransactionCancelled = null;
            UASInviteTransactionRequestAuthenticate = null;
            UASInviteTransactionRequestReceived = null;
            UASInviteTransactionTimedOut = null;
            CDR = null;
        }

        private void UASInviteTransaction_TransactionTimedOut(SIPTransaction sipTransaction)
        {
            if (UASInviteTransactionTimedOut != null)
            {
                UASInviteTransactionTimedOut(this);
            }
        }

        private void UASInviteTransaction_TransactionResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            logger.Warn("UASInviteTransaction received unexpected response, " + sipResponse.ReasonPhrase + " from " + IPSocket.GetSocketString(remoteEndPoint) + ", ignoring.");
        }

        private void UASInviteTransaction_TransactionRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPRequest sipRequest)
        {
            try
            {
                if (TransactionState == SIPTransactionStatesEnum.Terminated)
                {
                    logger.Debug("Request received by UASInviteTransaction for a terminated transaction, ignoring.");
                }
                else if (TransactionState == SIPTransactionStatesEnum.Trying || TransactionState == SIPTransactionStatesEnum.Proceeding )
                {
                    logger.Debug("Duplicate INVITE request received by UASInviteTransaction, ignoring.");
                }
                else if (sipRequest.Method != SIPMethodsEnum.INVITE)
                {
                    logger.Error("Unexpected " + sipRequest.Method + " passed to UASInviteTransaction.");
                }
                else
                {
                    SIPResponse tryingResponse = GetInfoResponse(m_transactionRequest.Header, SIPResponseStatusCodesEnum.Trying);
                    SendInformationalResponse(tryingResponse);

                    if (m_authenticationRequired && m_transactionRequest.Header.AuthenticationHeader == null)
                    {
                        // 407 Response with a fresh nonce needs to be sent.
                        SIPResponse authReqdResponse = GetAuthReqdResponse(sipRequest.Header, m_realm, Crypto.GetRandomInt().ToString());

                        //logger.Debug("Sending ProxyAuthenticationRequired response for " + sipRequest.Method + " to " + this.RemoteEndPoint + ", transaction source IP " + this.SendFromEndPoint + ".");

                        //UpdateTransactionState(SIPTransactionStatesEnum.Completed);
                        SendFinalResponse(authReqdResponse);
                    }
                    else
                    {
                        // Process INVITE.
                        if (m_authenticationRequired)
                        {
                            if (UASInviteTransactionRequestAuthenticate != null)
                            {
                                UASInviteTransactionRequestAuthenticate(localEndPoint, remoteEndPoint, this, sipRequest.Header.AuthenticationHeader);
                            }
                        }
                        else
                        {
                            if (UASInviteTransactionRequestReceived != null)
                            {
                                UASInviteTransactionRequestReceived(localEndPoint, remoteEndPoint, this, sipRequest);
                            }
                            else
                            {
                                // Nobody wants the call so return an error response.
                                SIPResponse declinedResponse = SIPTransport.GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Decline, "Nothing listening", this.SendFromEndPoint, this.RemoteEndPoint);
                                SendFinalResponse(declinedResponse);
                            }
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UASInviteTransaction GotRequest. " + excp.Message);
            }
        }

        public override void SendInformationalResponse(SIPResponse sipResponse)
        {
            base.SendInformationalResponse(sipResponse);

            if (CDR != null)
            {
                CDR.Progress(sipResponse.Status, sipResponse.ReasonPhrase);
            }
        }

        public override void SendFinalResponse(SIPResponse sipResponse)
        {
            base.SendFinalResponse(sipResponse);

            if (CDR != null)
            {
                CDR.Answered(sipResponse.Status, sipResponse.ReasonPhrase);
            }
        }

        public void CancelCall()
        {
            if (TransactionState == SIPTransactionStatesEnum.Proceeding)
            {
                base.Cancel();

                SIPResponse cancelResponse = SIPTransport.GetResponse(TransactionRequest.Header, SIPResponseStatusCodesEnum.RequestTerminated, null, SendFromEndPoint, RemoteEndPoint);
                SendFinalResponse(cancelResponse);

                if (UASInviteTransactionCancelled != null)
                {
                    UASInviteTransactionCancelled(this);
                }
            }
            else
            {
                logger.Warn("A request was made to cancel transaction " + TransactionId + " that was not in the proceeding state, state=" + TransactionState + ".");
            }
        }

        public SIPResponse GetAuthReqdResponse(SIPHeader requestHeader, string realm, string nonce)
        {
            try
            {
                if (requestHeader == null)
                {
                    throw new ApplicationException("Cannot build Authorisation Required response from an empty request header.");
                }

                SIPResponse authReqdResponse = new SIPResponse(SIPResponseStatusCodesEnum.ProxyAuthenticationRequired);
                SIPAuthenticationHeader authHeader = new SIPAuthenticationHeader(realm, nonce);

                SIPHeader unauthHeader = new SIPHeader(requestHeader.Contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
                unauthHeader.CSeqMethod = requestHeader.CSeqMethod;
                unauthHeader.Via = requestHeader.Via;
                unauthHeader.ProxyAuthenticate = true;
                unauthHeader.AuthenticationHeader = authHeader;
                unauthHeader.UserAgent = m_sipProxyUserAgent;
                unauthHeader.MaxForwards = Int32.MinValue;

                authReqdResponse.Header = unauthHeader;

                return authReqdResponse;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetAuthReqdResponse. " + excp.Message);
                throw excp;
            }
        }

        public SIPResponse GetOkResponse(SIPHeader requestHeader, IPEndPoint localEndPoint, string messageBody)
        {
            try
            {
                SIPResponse okResponse = new SIPResponse(SIPResponseStatusCodesEnum.Ok);

                SIPContactHeader contact = new SIPContactHeader(null, SIPURI.ParseSIPURI("sip:" + SendFromEndPoint));

                okResponse.Header = new SIPHeader(SIPContactHeader.ParseContactHeader("sip:" + IPSocket.GetSocketString(localEndPoint)), requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
                okResponse.Header.To.ToTag = m_localTag;
                okResponse.Header.CSeqMethod = requestHeader.CSeqMethod;
                okResponse.Header.Via = requestHeader.Via;
                okResponse.Header.Server = m_sipProxyUserAgent;
                okResponse.Header.MaxForwards = Int32.MinValue;
                okResponse.Header.RecordRoutes = requestHeader.RecordRoutes;
                okResponse.Body = messageBody;
                okResponse.Header.ContentType = "application/sdp";
                okResponse.Header.ContentLength = messageBody.Length;

                return okResponse;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetOkResponse. " + excp.Message);
                throw excp;
            }
        }
	}
}
